package eval.engine.FireFighting;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import eval.deploy.IDemandManager;
import eval.engine.IPlacement;
import eval.engine.cplex.CplexMapIndexToVmId;
import eval.engine.cplex.InitialCplexCode.CplexDataHolder;
import eval.host.Host;
import eval.host.IHostManager;
import eval.vm.VMIdentifier;
import eval.vm.VirtualMachine;


public class FireFightingPlacement implements IPlacement {
	
	private Map<Host, List<VirtualMachine>> vmMapping = new HashMap<Host, List<VirtualMachine>>();
	private Map<Integer,List<VMIdentifier> > CloudMap = new HashMap<Integer,List<VMIdentifier> >();
	//private Set<Integer , List<VMIdentifier>>  CloudSet =  new HashSet<Integer, List<VMIdentifier>>();
	private Map<VirtualMachine, Integer> CPUDemandOfVMs;
	private Map<VirtualMachine, Integer> MEMDemandOfVMs; 
	
	//private List<VirtualMachine> VMSinCloud ;
	private List<VirtualMachine> vmsWithSatisfiedDemand = new ArrayList<VirtualMachine>();
	private List<VirtualMachine> vmsWithUNSatisfiedDemand = new ArrayList<VirtualMachine>();
	private List<VirtualMachine> VMSrequestedTobedeployed;
	private List<VirtualMachine> RejectedVMS;
	private List<VirtualMachine> PlacedVMS;
	
	private List<Host> turendONHosts = new ArrayList<Host>();
	private List<Host> turendOFFHosts = new ArrayList<Host>();
	private List<Integer> OpenHosts = new ArrayList<Integer>();
	private int[] HostLoadAtPlacement;	
	
	private int tick;
	private IPlacement lastPlacement; 
	private IDemandManager demandManager;
	private IHostManager hostManager;
	private List<VirtualMachine> removedList;
	
	private int numberOfVMSinCloud = 0;
	private boolean emptyPlacement = false;
//	private int totalRequestedCPU = 0;
	private int totalCloudCPULoad = 0;
	private int totalCloudCpuDemand = 0;
	private int totalCloudMemDemand = 0;
	private double minCpuLoadInHosts = Double.POSITIVE_INFINITY;
	private double maxCpuLoadInHosts = Double.NEGATIVE_INFINITY;
	private double minMemLoadInHosts = Double.POSITIVE_INFINITY;
	private double maxMemLoadInHosts = Double.NEGATIVE_INFINITY;
//	private double avgCPULoadInHosts = 0;
//	private double avgMEMLoadInHosts = 0;
	private int totalSatisfiedDemand = 0;
	private int totalUNSatisfiedDemand = 0;
	private int numberOfOpenHosts = 0;
	private int numberOfCloseHosts = 0;
	private int numberOfCandidateHosts = 0;
	private int vmRelocate;
	private int totalCpuCapcityAtOpenHosts = 0;
	private int totalCpuCapcityAtCloud = 0;
	private int totalMemCapcityAtOpenHosts=0;
	private int totalCloudMemAtCloud = 0;
	private int baseHostCost = 0;
	private double hostsStartUpCost = 0.0;
	private int totalRequestedCPU = 0;
	private double sumOfMinREservtion = 0.0;

	public FireFightingPlacement(Map<Host, List<VirtualMachine>> vmMapping_, IHostManager hostManager_,
			IPlacement lastPlacement_, IDemandManager demandManager_  , List<VirtualMachine> removedList_
			 ,List<VirtualMachine> vmsToDeploy_,List<VirtualMachine> PlacedVMS_ ,List<VirtualMachine> RejectedVMS_
			 ,int tick_, List<VirtualMachine> vmRelocate_) {

		this.lastPlacement = lastPlacement_;
		this.vmMapping = vmMapping_;
		this.hostManager = hostManager_;
		this.removedList = removedList_;
		this.VMSrequestedTobedeployed = vmsToDeploy_;
		this.PlacedVMS = PlacedVMS_;
		this.RejectedVMS = RejectedVMS_;
		this.demandManager = demandManager_;
		if(VMSrequestedTobedeployed.size() != (PlacedVMS.size() +RejectedVMS.size()))
			System.out.println("Error at tick: "+tick);
		this.tick = tick_;
		this.CPUDemandOfVMs = demandManager.getCPUDemandOfAllVMsAtTick(tick);
		this.MEMDemandOfVMs = demandManager.getMEMDemandOfAllVMsAtTick(tick);
		HostLoadAtPlacement = new int[hostManager.getNumberOfHosts()];
		this.vmRelocate = vmRelocate_.size();
		updateCloudMap(vmMapping_);
		//updateVMmapping(vmMapping_);
		updatePlacement();
	}
	
	private void setTurendONHosts() {
		if(tick == 0) return;
		//turendONHosts.clear();
		for(Host curHost : 	vmMapping.keySet() ){
			//Host HostFromLastTick = getHostFromLastPlacement(curHost);
			if(curHost.isOpen() || curHost.isCandidateForClosing() ){
				OpenHosts.add(curHost.getHostId());
				if(!lastPlacement.getOpenHosts().contains(curHost.getHostId())){
					turendONHosts.add(curHost);
				}
			}
			
//			if(lastPlacement.isHostClose(curHost) && (curHost.isOpen() || curHost.isCandidateForClosing() ))
//					turendONHosts.add(curHost);	
		}
	}
	
	private void setTurendOFFHosts() {
		if(tick == 0) return;
		//turendOFFHosts.clear();
		for(Host curHost : 	vmMapping.keySet() ){
			//Host HostFromLastTick = getHostFromLastPlacement(curHost);
			if(lastPlacement.getOpenHosts().contains(curHost.getHostId()) && (!this.getOpenHosts().contains(curHost.getHostId()))){
				turendOFFHosts.add(curHost);
			}
			
//			if((HostFromLastTick.isOpen() || HostFromLastTick.isCandidateForClosing() ) && curHost.isClose() )
//					turendOFFHosts.add(curHost);	
		}
	}

	private void updateCloudMap(Map<Host, List<VirtualMachine>> vmMAP){
		for(Host host : vmMAP.keySet()){
			CloudMap.put(host.getHostId(), new ArrayList<VMIdentifier>());
			
			for(VirtualMachine vm : vmMAP.get(host)){
				CloudMap.get(host.getHostId()).add(vm.getIdentifier());
			}
		}
		
	}
	/*
	private void updateVMmapping(Map<Host, List<VirtualMachine>> vmMAP){
		for(Host host :hostManager.getHosts()) {
			if(host.isOpen()) {
				vmMapping.put(host, hostManager.getVmsAtHost(host));
			}
		}
		
	}*/
	
	
	private void updatePlacement(){
		
		double sumOfMEMinHost = 0;
				// iterates over hosts
				for (Host host : vmMapping.keySet()) {
					hostManager.updateCurrentHostDemand(demandManager, host, vmMapping.get(host), tick);
					sumOfMEMinHost = 0;
					//numberOfVMSinCloud += vmMapping.get(host).size();
					HostLoadAtPlacement[host.getHostId()] = host.getCurrentCpuLoad();
					
					if(host.isOpen()){
						numberOfOpenHosts++;}
					else if(host.isClose()){
						numberOfCloseHosts++;}
					else{numberOfCandidateHosts++;}
					
					if(host.getCurrentCpuLoad() < minCpuLoadInHosts){
						minCpuLoadInHosts = host.getCurrentCpuLoad();
					}
					if(host.getCurrentCpuLoad() > maxCpuLoadInHosts){
						maxCpuLoadInHosts = host.getCurrentCpuLoad();
					}
					
					// iterates over vms within the host
					for (VirtualMachine vm : vmMapping.get(host)) {
						sumOfMinREservtion+= vm.getVMConfiguration().getMinCpu();
						numberOfVMSinCloud++;
						
						totalCloudCPULoad += vm.getSatisfiedCpuDemand();
						
						totalCloudCpuDemand +=  CPUDemandOfVMs.get(vm);//  demandManager.getCPUDemandOfVmAtTick(vm,tick);
						
						totalCloudMemDemand += MEMDemandOfVMs.get(vm);
						
						sumOfMEMinHost += MEMDemandOfVMs.get(vm);
						
						int CurVmCons = vm.getSatisfiedCpuDemand();
						if(CurVmCons == CPUDemandOfVMs.get(vm) ||
								CurVmCons < vm.getVMConfiguration().getMinCpu()){
							vmsWithSatisfiedDemand.add(vm);
							totalSatisfiedDemand += CurVmCons;
							int unSatCpuChk = CPUDemandOfVMs.get(vm).intValue()-vm.getSatisfiedCpuDemand();
							if(unSatCpuChk>0)
								totalUNSatisfiedDemand += unSatCpuChk;
						}
						else{
							vmsWithUNSatisfiedDemand.add(vm);
							totalUNSatisfiedDemand += CPUDemandOfVMs.get(vm) - CurVmCons;
							int unSatCpuChk = vm.getSatisfiedCpuDemand();
							if(unSatCpuChk>0)
								totalSatisfiedDemand += unSatCpuChk;
						}
							
							
					}//end: iterates over vms within the host
					
					if(sumOfMEMinHost < minMemLoadInHosts){
						minMemLoadInHosts = sumOfMEMinHost;
					}
					if(sumOfMEMinHost > maxMemLoadInHosts){
						maxMemLoadInHosts = sumOfMEMinHost;
					}
					
				}//end: iterates over hosts
				
				setTurendONHosts();
				setTurendOFFHosts();
				List<Host> hosts =  hostManager.getOpenHosts();
				for(Host it : hosts) {
					totalCpuCapcityAtOpenHosts += it.getHostCapacity().getCpuCapacity();
					totalMemCapcityAtOpenHosts += it.getHostCapacity().getMemCapacity();
					baseHostCost += hostManager.getBaseCostForHost(it.getHostCapacityEnum());
				}
				int total = 0;
				hosts =  hostManager.getHosts();
				for(Host it : hosts) {
					totalCpuCapcityAtCloud += it.getHostCapacity().getCpuCapacity();
					totalCloudMemAtCloud += it.getHostCapacity().getMemCapacity(); 
				}
				hosts =  getTurnedONHosts();
				for(Host it : hosts) {
					hostsStartUpCost += hostManager.getStartUpCostForHost(it.getHostCapacityEnum());
				}
				//the Requested CPU include the cpu demand of vms that have been rejected,so we must add them to the calculation.
				if(!RejectedVMS.isEmpty()){
					for(VirtualMachine vm : RejectedVMS){
						totalRequestedCPU += demandManager.getCPUDemandOfVmAtTick(vm, tick);
					}
				}
				totalRequestedCPU += totalCloudCpuDemand;
				
			//	System.out.println("\n number of rejected VMS: " + RejectedVMS.size() );
			//	System.out.println("\n number of turned on hosts: " + turendONHosts.size() );
	
	}

	@Override
	public int getTotalRequestedCPU() {
		return totalRequestedCPU ;
	}

	@Override
	public List<Integer> getOpenHosts(){
		return OpenHosts;
	}
	
	@Override
	public int getTotalCloudCPULoad() {
		return totalCloudCPULoad;
	}

	@Override
	public int getTotalCloudCpuDemand() {
		return totalCloudCpuDemand;
	}

	@Override
	public int getTotalCloudMemDemand() {
		return totalCloudMemDemand;
	}

	@Override
	public List<Host> getTurnedONHosts() {
		return turendONHosts;
	}

	@Override
	public List<Host> getTurnedOFFHosts(){
		return turendOFFHosts;
	}


	@Override
	public int getNumberOfOpenHosts() {
		return numberOfOpenHosts;
	}

	@Override
	public int getNumberOfClosedHosts() {
		return numberOfCloseHosts;
	}

	@Override
	public int getNumberOfCandidatesForClosingHosts() {
		return numberOfCandidateHosts;
	}

	@Override
	public int getNumberOfTurnOffHosts() {
		return turendOFFHosts.size();
	}

	@Override
	public int getNumberOfTurnOnHosts() {
		return turendONHosts.size();
	}
	
	@Override
	public double getMinCPULoadInHosts() {
		return minCpuLoadInHosts;
	}

	@Override
	public double getMinMEMLoadInHosts() {
		return minMemLoadInHosts;
	}

	@Override
	public double getAvgCPULoadInHosts() {
		return (totalCloudCPULoad / hostManager.getNumberOfHosts());
	}

	@Override
	public double getAvgMEMLoadInHosts() {
		return (totalCloudMemDemand / hostManager.getNumberOfHosts());
	}

	@Override
	public double getMaxCPULoadInHosts() {
		return maxCpuLoadInHosts;
	}

	@Override
	public double getMaxMEMLoadInHosts() {
		return maxMemLoadInHosts;
	}

	@Override
	public List<VirtualMachine> getVMSrequestedTobedeployed() {
		return VMSrequestedTobedeployed;
	}

	@Override
	public List<VirtualMachine> getVMSWithSatisfiedDemand() {
		return vmsWithSatisfiedDemand;
	}

	@Override
	public List<VirtualMachine> getVMSWithUNSatisfiedDemand() {
		return vmsWithUNSatisfiedDemand;
	}

	@Override
	public List<VirtualMachine> getRejectedVMS() {
		return RejectedVMS;
	}

	@Override
	public List<VirtualMachine> getRemovedList() {
		return removedList;
	}
	
	@Override
	public List<VirtualMachine> getPlacedList() {
		return PlacedVMS;
	}

	@Override
	public int getNumberOfVMsAtCloud() {
		return numberOfVMSinCloud;
	}

	@Override
	public int getNumberOfVMsWithSatisfiedDemand() {
		return vmsWithSatisfiedDemand.size();
	}

	@Override
	public int getNumberOfVMsWithUnSatisfiedDemand() {
		return vmsWithUNSatisfiedDemand.size();
	}

	@Override
	public int getTotalSatisfiedCPUDemand() {
		return totalSatisfiedDemand;
	}

	@Override
	public int getTotalUnSatisfiedCPUDemand() {
		return totalUNSatisfiedDemand;
	}

	@Override
	public int getNumberOfRemovedVMs() {
		return removedList.size();
	}

	@Override
	public int getNumberOfPlacedVMs() {
		return PlacedVMS.size();
	}
	/*
	@Override
	public int[] getHostLoadAtPlacement() {
		return HostLoadAtPlacement;
	}*/

	@Override
	public boolean isEmptyPlacement() {
		return emptyPlacement;
	}
	
	@Override
	public Map<Host, List<VirtualMachine>> getvmMapping(){
		return this.vmMapping;
	}
	
	@Override
	public Map<Integer, List<VMIdentifier>> getCloudMap(){
		return this.CloudMap;
	}
	
	@Override
	public boolean isHostOpen(Host host){
		for(Host iterHost : vmMapping.keySet()){
			if(iterHost.getHostId() == host.getHostId() && iterHost.isOpen())
				return true;
		}
		return false;
	}
	
	@Override
	public boolean isHostClose(Host host){
		for(Host iterHost : getvmMapping().keySet()){
			if(iterHost.getHostId() == host.getHostId() && iterHost.isClose())
				return true;
		}
		return false;
	}
	
	@Override
	public Host getHostFromLastPlacement(Host host){
		for(Host iterHost : lastPlacement.getvmMapping().keySet()){
			if(iterHost.getHostId() == host.getHostId() )
				return iterHost;
		}
		return null;
	}

	@Override
	public int getNumberOfVmsReqToBeDeploy() {
		return getVMSrequestedTobedeployed().size();
	}

	@Override
	public int getTotalCpuCapcityAtOpenHosts() {
		return totalCpuCapcityAtOpenHosts;
	}

	@Override
	public int getTotalCpuCapcityAtCloud() {
		return totalCpuCapcityAtCloud;
	}

	@Override
	public int getTotalMemCapcityAtOpenHosts() {
		
		return totalMemCapcityAtOpenHosts;
	}

	@Override
	public int getTotalCloudMemAtCloud() {
		return totalCloudMemAtCloud;
	}
	
	@Override
	public double getBaseHostCost() {
		return baseHostCost;
	}
	
	@Override
	public double getHostsStartUpCost() {
		return hostsStartUpCost;
	}
	
	@Override
	public double getWorkingHostCost() {
		return hostManager.getLoadCost(totalCloudCPULoad);
		//TODO: Amit
		//return hostManager.getLoadCost(getNumberOfOpenHosts());
	}

	@Override
	public double getIncomeForSatisfyDemand() {
		return hostManager.getIncomeOnLoad(getTotalCloudCPULoad());
	}

	@Override
	public int getTotalMemLoadAtCloud() {
		return hostManager.getToatalMemLoadOnCloud();
	}

	@Override
	public double getSumOfMinReservtion() {
		return  sumOfMinREservtion;
	}

	

	@Override
	public CplexMapIndexToVmId getCplexMapIndexToVmId() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CplexDataHolder getCplexData() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getVmRelocate() {
		return vmRelocate;
	}


	
}